package com.nulldev.util.JVM.reflections.cache;

import java.util.Arrays;
import java.util.Map;
import java.util.Set;

import com.nulldev.util.data.Arrays.maps.trie.TrieMap;

import static com.nulldev.util.JVM.reflections.cache.CachedReflection.FACTORY;

final class Util {
	protected static final Map<Class<?>, Class<?>> CONV_PRIMITIVE = CachedPrimitive.CONV_PRIMITIVE;
	protected static final Set<Class<?>> PRIMITIVE = CachedPrimitive.PRIMITIVE;
	protected static final Object OBJECT = CachedPrimitive.OBJECT;
	protected static final Object STRING = CachedPrimitive.STRING;
	protected static final Set<Class<?>> NUMBER_PRIMITIVE = CachedPrimitive.NUMBER_PRIMITIVE;

	protected static final Map<Class<?>, Integer> CONV_TABLE;

	static {
		final Map<Class<?>, Integer> m = new TrieMap<Class<?>, Integer>();
		m.put(Byte.class, 0);
		m.put(Character.class, 1);
		m.put(Short.class, 2);
		m.put(Integer.class, 3);
		m.put(Long.class, 4);
		m.put(Float.class, 5);
		m.put(Double.class, 6);
		CONV_TABLE = m;
	}

	public static final Class<?>[] getParamsType(final Object... args) {
		int argsLen;
		if (args == null || (argsLen = args.length) <= 0) {
			return CachedPrimitive.NO_PARAM_CLASS;
		}
		final Class<?>[] c = new Class[argsLen];
		Object o;
		for (int i = 0; i < argsLen; i++) {
			if ((o = args[i]) != null) {
				c[i] = o.getClass();
			}
		}
		return c;
	}

	public static final Class<?>[] getParamsTypeByClass(final Class<?>[] args) {
		int argsLen;
		if (args == null || (argsLen = args.length) <= 0) {
			return CachedPrimitive.NO_PARAM_CLASS;
		}
		Class<?> pc;
		for (int i = 0; i < argsLen; i++) {
			if (args[i] != null) {
				pc = CONV_PRIMITIVE.get(args[i]);
				if (pc != null) {
					args[i] = pc;
				}
			}
		}
		return args;
	}

	public static final int parmasScore(final boolean[] pms, final Class<?>[] src, final Class<?>[] dest, final Object[] args, final ClassLoader cl) {
		int ret = 0;
		int lenJ;
		Class<?> s, d;
		Object a;
		String ss;
		boolean one = true;
		int len = src.length;
		for (int i = 0; i < len; i++) {
			s = src[i];
			d = dest[i];
			a = args[i];
			if (d != null) {
				if (s == d) {
					ret += 100;
				} else if (s == OBJECT) {
					ret += 60;
				} else if (NUMBER_PRIMITIVE.contains(s) && NUMBER_PRIMITIVE.contains(d)) {
					ret += 50;
				} else if (s == STRING && (PRIMITIVE.contains(d))) {
					ret += 40;
				} else if (d == STRING && (PRIMITIVE.contains(s))) {
					ss = (String) a;
					if ((s == Boolean.class && ("true".equals(ss) || "false".equals(ss))) || isNumber(ss)) {
						ret += 40;
					} else {
						return -1;
					}
				} else {
					ClassElement em = FACTORY.getClass(d.getName());
					one = false;
					Class<?> o;
					String[] ifce;
					int wScore = 100;
					if (s.isInterface()) {
						int befWScore;
						String sname = s.getName();
						while (true) {
							wScore--;
							befWScore = wScore;
							ifce = em.getInterfaseNames();
							if (ifce != null && (lenJ = ifce.length) > 0) {
								if (Arrays.binarySearch(ifce, sname) >= 0) {
									one = true;
									ret += wScore;
								} else if (lenJ == 1) {
									if ((wScore = toInterface(wScore, sname, ifce[0], cl)) != -1) {
										one = true;
										ret += wScore;
									}
								} else {
									for (int j = 0; j < lenJ; j++) {
										if ((wScore = toInterface(wScore, sname, ifce[i], cl)) != -1) {
											one = true;
											ret += wScore;
											break;
										}
									}
								}
								if (one) {
									break;
								}
							}
							wScore = befWScore;
							em = FACTORY.getClass(em.getSuperClassName());
							if ((o = em.getClassObject()) == OBJECT) {
								return -1;
							} else if (o == s) {
								one = true;
								ret += wScore;
								break;
							}
						}
					} else {
						while (true) {
							wScore--;
							em = FACTORY.getClass(em.getSuperClassName());
							if ((o = em.getClassObject()) == OBJECT) {
								return -1;
							} else if (o == s) {
								one = true;
								ret += wScore;
								break;
							}
						}
					}
					if (!one) {
						return -1;
					}
				}
			} else if (pms[i]) {
				return -1;
			}
		}
		return ret;
	}

	private static final int toInterface(int wScore, final String sname, final String name, final ClassLoader cl) {
		ClassElement em = FACTORY.getClass(cl, name);
		while (em != null) {
			wScore--;
			String superClassName = em.getSuperClassName();
			if (superClassName == sname) {
				return wScore;
			}
			em = FACTORY.getClass(cl, superClassName);
		}
		return -1;
	}

	public static final Object[] convertParams(final Object[] args, final Class<?>[] types) {
		int len = args.length;
		Class<?> s, d;
		Object[] ret;
		if (len > 0) {
			ret = new Object[len];
			System.arraycopy(args, 0, ret, 0, len);
		} else {
			return CachedPrimitive.NO_PARAM;
		}
		for (int i = 0; i < len; i++) {
			if (ret[i] != null) {
				s = types[i];
				d = ret[i].getClass();
				if (s != d) {
					if (NUMBER_PRIMITIVE.contains(s) && NUMBER_PRIMITIVE.contains(d)) {
						ret[i] = convertNumberPrimitive(ret[i], CONV_TABLE.get(s), CONV_TABLE.get(d));
					} else if (s == STRING && (PRIMITIVE.contains(d))) {
						ret[i] = ret[i].toString();
					} else if (d == STRING && (PRIMITIVE.contains(s))) {
						if (s == Boolean.class) {
							String str = (String) args[i];
							if (str.equals("true")) {
								ret[i] = Boolean.TRUE;
							} else if (str.equals("false")) {
								ret[i] = Boolean.FALSE;
							} else {
								throw new CachedReflectException("The cast of the " + (i + 1) + " argument failed");
							}
						} else {
							Integer o = CONV_TABLE.get(s);
							if (o != null) {
								if (isNumber((String) ret[i])) {
									ret[i] = convertNumber(o, (String) ret[i]);
								} else {
									return null;
								}
							}
						}
					}
				}
			}
		}
		return ret;
	}

	private static final Object convertNumber(int type, String s) {
		s = s.trim().toLowerCase();
		if (s.endsWith("f") || s.endsWith("l")) {
			s = s.substring(0, s.length() - 1);
		}
		if (type == 5 || type == 6) {
			return convertType(type, s);
		}
		int p = s.indexOf(".");
		if (p == -1) {
			if (s.startsWith("0x")) {
				s = s.substring(2);
				int len = s.length();
				if (len > 8) {
					if (len > 16) {
						throw new CachedReflectException("Numerical conversion failed.");
					}
					long ret = 0L;
					for (int i = 0; i < len; i++) {
						char c = s.charAt(i);
						if (c >= '1' && c <= '9') {
							ret |= ((int) (c - '0') << i);
						} else if (c >= 'a' && c <= 'f') {
							ret |= ((int) ((c - 'a') + 10) << i);
						}
					}
					switch (type) {
						case 0:
							return (byte) (ret & 0x00000000000000ffL);
						case 1:
							return (char) (ret & 0x000000000000ffffL);
						case 2:
							return (short) (ret & 0x000000000000ffffL);
						case 3:
							return (int) (ret & 0x00000000ffffffffL);
						case 4:
							return ret;
					}
					return null;
				} else {
					int ret = 0;
					for (int i = 0; i < len; i++) {
						char c = s.charAt(i);
						if (c >= '1' && c <= '9') {
							ret |= ((int) (c - '0') << i);
						} else if (c >= 'a' && c <= 'f') {
							ret |= ((int) ((c - 'a') + 10) << i);
						}
					}
					switch (type) {
						case 0:
							return (byte) (ret & 0x000000ff);
						case 1:
							return (char) (ret & 0x0000ffff);
						case 2:
							return (short) (ret & 0x0000ffff);
						case 3:
							return ret;
						case 4:
							return (long) ret;
					}
					return null;
				}
			}
			return convertType(type, s);
		}
		return convertType(type, s.substring(0, p));
	}

	private static final Object convertType(final int type, final String s) {
		switch (type) {
			case 0:
				return Byte.parseByte(s);
			case 1:
				return (s.length() == 1) ? s.charAt(0) : (char) (Integer.parseInt(s) & 0x0000ffff);
			case 2:
				return Short.parseShort(s);
			case 3:
				return Integer.parseInt(s);
			case 4:
				return Long.parseLong(s);
			case 5:
				return Float.parseFloat(s);
			case 6:
				return Double.parseDouble(s);
		}
		return s;
	}

	private static final Object convertNumberPrimitive(final Object o, final int srcType, final int destType) {
		switch (destType) {
			case 0: {
				byte x = (Byte) o;
				switch (srcType) {
					case 0:
						return x;
					case 1:
						return (char) x;
					case 2:
						return (short) x;
					case 3:
						return (int) x;
					case 4:
						return (long) x;
					case 5:
						return (float) x;
					case 6:
						return (double) x;
				}
			}
			case 1: {
				char x = (Character) o;
				switch (srcType) {
					case 0:
						return (byte) x;
					case 1:
						return x;
					case 2:
						return (short) x;
					case 3:
						return (int) x;
					case 4:
						return (long) x;
					case 5:
						return (float) x;
					case 6:
						return (double) x;
				}
			}
			case 2: {
				short x = (Short) o;
				switch (srcType) {
					case 0:
						return (byte) x;
					case 1:
						return (char) x;
					case 2:
						return x;
					case 3:
						return (int) x;
					case 4:
						return (long) x;
					case 5:
						return (float) x;
					case 6:
						return (double) x;
				}
			}
			case 3: {
				int x = (Integer) o;
				switch (srcType) {
					case 0:
						return (byte) x;
					case 1:
						return (char) x;
					case 2:
						return (short) x;
					case 3:
						return x;
					case 4:
						return (long) x;
					case 5:
						return (float) x;
					case 6:
						return (double) x;
				}
			}
			case 4: {
				long x = (Long) o;
				switch (srcType) {
					case 0:
						return (byte) x;
					case 1:
						return (char) x;
					case 2:
						return (short) x;
					case 3:
						return (int) x;
					case 4:
						return x;
					case 5:
						return (float) x;
					case 6:
						return (double) x;
				}
			}
			case 5: {
				float x = (Float) o;
				switch (srcType) {
					case 0:
						return (byte) x;
					case 1:
						return (char) x;
					case 2:
						return (short) x;
					case 3:
						return (int) x;
					case 4:
						return (long) x;
					case 5:
						return x;
					case 6:
						return (double) x;
				}
			}
			case 6: {
				double x = (Double) o;
				switch (srcType) {
					case 0:
						return (byte) x;
					case 1:
						return (char) x;
					case 2:
						return (short) x;
					case 3:
						return (int) x;
					case 4:
						return (long) x;
					case 5:
						return (float) x;
					case 6:
						return x;
				}
			}
		}
		return o;
	}

	private static final boolean isNumber(final String num) {
		if (num == null || num.length() <= 0) {
			return false;
		}
		int start = 0;
		if (num.startsWith("-")) {
			start = 1;
		}
		boolean dt = false;
		int len = num.length();
		if (start < len) {
			for (int i = start; i < len; i++) {
				char c = num.charAt(i);
				if (c == '.') {
					if (dt) {
						return false;
					}
					dt = true;
				} else if ((c >= '0' && c <= '9') == false) {
					return false;
				}
			}
		} else {
			return false;
		}
		return true;
	}
}
